
`include "common_header.verilog"

//  *************************************************************************
//  File : mac40_tx_ipg.v
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited.
//  Copyright (c) 2014 MorethanIP
//  Muenchner Strasse 199, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Daniel Koehler, Thomas Schrobenhauser
//  info@morethanip.com
//  *************************************************************************
//  Description : IPG calculation at end of frame pausing transmit statemachine.
//  Version     : $Id: mac40_tx_ipg.v,v 1.5 2014/10/27 17:03:59 ts Exp $
//  *************************************************************************

module mac40_tx_ipg (
   reset_txclk,
   xlgmii_txclk,
   xlgmii_txclk_ena,
   tx_enable,
   xgmii_mode,
   ipg_comp_cnt,
   txipg_eop,
   txipg_mod,
   txipg_crc,
   txipg_sub1,
   txipg_done,
   txipg_dic,
   txipg_norm
   );

`include "mtip_40geth_pack_package.verilog"

input   reset_txclk;            //  Active high reset for xlgmii_txclk domain
input   xlgmii_txclk;           //  XLGMII transmit clock
input   xlgmii_txclk_ena;       //  XLGMII transmit clock enable
input   tx_enable;              //  Enable / disable MAC transmit path
input   xgmii_mode;             //  XGMII mode enable (0=XLGMII, 1=XGMII)
input   [15:0] ipg_comp_cnt;    //  IPG compensation count
input   [1:0] txipg_eop;        //  End of frame for Tx IPG calculation
input   [2:0] txipg_mod;        //  Last word modulo, valid when txipg_eop asserts
input   txipg_crc;              //  MAC appends CRC
input   [1:0] txipg_sub1;       //  Idle block was removed, return to normal operation
output  txipg_done;             //  Tx IPG done, next frame can be sent
output  [2:0] txipg_dic;        //  current DIC
output  [1:0] txipg_norm;       //  1:normal, 0:request to remove 1 block to compensate for MLD

reg     txipg_done;
wire    [2:0] txipg_dic;
wire    [1:0] txipg_norm;

wire    frame_eop;              //  end of frame pulse
reg     [2:0] dic_cnt;          //  current DIC
wire    [2:0] dic_inc;          //  increment value
wire    [3:0] dic_plus;         //  adder with carry
wire    dic_add1;
wire    dic_add1_norm;
wire    dic_mod4;
wire    dic_m4;
wire    dic_a4;
wire    do_shft_set;
wire    no_shft_clr;
wire    [1:0] mld_sub1;         //  indicate subtract 1 will be done
reg     mld_norm40;             //  1:normal, 0:request to remove 1 block to compensate for MLD
reg     mld_norm25;
reg     [15:0] mld_cnt_l;
reg     mld_cnt_tog;
wire    mld_cnt_l_max40;
wire    mld_cnt_l_max25;
wire    mld_cnt_l_max;          //  counter max reached


//  End of frame indication
//  -----------------------
assign frame_eop = txipg_eop[0];

assign txipg_dic = dic_cnt;
assign txipg_norm[0] = mld_norm40;                // 40G marker compensation
assign txipg_norm[1] = mld_norm25 | dic_cnt[2];   // 25G marker compensation (Case 2.1, crc=0)


//  DIC counter
//  -----------
assign dic_inc = {~txipg_mod[2], txipg_mod[1:0]};

assign dic_plus = {1'b 0, dic_cnt} + {1'b 0, dic_inc};   //  get carry and remainder

always @(posedge xlgmii_txclk or posedge reset_txclk)
   begin : pdic
   if (reset_txclk == 1'b 1)
      begin
      dic_cnt <= 3'b 000;
      end
   else
      begin
      //  CLOCK ENABLE
      if (xlgmii_txclk_ena == 1'b 1)
         begin
         if (tx_enable == 1'b 0)
            begin
            dic_cnt <= 3'b 000;
            end
         else if (frame_eop == 1'b 1)
            begin
            if (xgmii_mode == 1'b 1 & dic_m4 == 1'b 1)     // 25G marker compensation
               begin
               dic_cnt <= {~dic_plus[2], dic_plus[1:0]};   // dic_plus[2]=1, subtract 4
               end
            else if (xgmii_mode == 1'b 1 & dic_a4 == 1'b 1)
               begin
               dic_cnt <= {~dic_cnt[2], dic_cnt[1:0]};     // dic_cnt[2]=0, add 4
               end
            else
               begin
               //  keep remainder
               dic_cnt <= dic_plus[2:0];
               end
            end
         end
      end
   end

assign dic_add1 = (frame_eop == 1'b 1 & dic_plus[3] == 1'b 1 & txipg_crc == 1'b 1 &
                   txipg_mod != 3'b 100) |
                  (frame_eop == 1'b 1 & dic_plus[3] == 1'b 1 & txipg_crc == 1'b 0 &
                   (txipg_mod == 3'b 000 |
                   (txipg_mod[2] == 1'b 1 & txipg_mod[1:0] != 2'b 00))) |
                  (frame_eop == 1'b 1 & txipg_eop[1] == 1'b 1) ? 1'b 1 : 1'b 0;

assign dic_add1_norm = dic_add1 & mld_norm40 &
                       ~dic_a4;   // 25G marker compensation (Case 2.1, crc=1 (txipg_eop[1]=1))


//  Generate IPG with DIC
//  ---------------------
always @(posedge xlgmii_txclk or posedge reset_txclk)
   begin : pipg
   if (reset_txclk == 1'b 1)
      begin
      txipg_done <= 1'b 1;
      end
   else
      begin
      //  CLOCK ENABLE
      if (xlgmii_txclk_ena == 1'b 1)
         begin

         //  finally decide if extra IPG cycles are necessary at the end of the frame and stop STM accordingly.
         //  --------------------------------------------------------------------------------------------------
         if (frame_eop == 1'b 1 & dic_add1_norm == 1'b 1)
            begin
            //  need to add 2nd column when full word (mod==0) or mod > 4 and DIC overflows
            txipg_done <= 1'b 0;
            end
         else
            begin
            txipg_done <= 1'b 1;
            end
         end
      end
   end


//  PCS Marker compensation
//  -----------------------
//  40G: Need to remove one 8-byte idle block after every 16383 (0x3fff) transfers to compensate
//       for 40G marker insertions.
//  25G with RS-FEC: Need to remove one 8-byte idle block after every 20479 (0x4fff) transfers to
//       compensate for 25G marker insertions. Since on the XGMII interface, only 4-byte idle blocks
//       can be deleted, need to remove 2 x 4-byte idle blocks after every 20479 transfers. Hence
//       the compare count value is internally divided by 2.
//  10G or 25G without RS-FEC: No compensation needed.
//  Compare count value can be directly configured in register TX_IPG_LENGTH[31:16].
//  --------------------------------------------------------------------------------

assign mld_sub1[0] = (dic_add1 == 1'b 1 & mld_norm40 == 1'b 0) ? 1'b 1 : 1'b 0;   // whenever DIC wants to add one block
assign mld_sub1[1] = (dic_add1 == 1'b 1 & dic_a4     == 1'b 1) ? 1'b 1 : 1'b 0;

assign mld_cnt_l_max40 = (xgmii_mode == 1'b 0 & mld_cnt_l == ipg_comp_cnt) ? 1'b 1 : 1'b 0;
assign mld_cnt_l_max25 = (xgmii_mode == 1'b 1 & mld_cnt_l == {1'b 0, ipg_comp_cnt[15:1]}) ? 1'b 1 : 1'b 0;   // cnt/2

assign mld_cnt_l_max = mld_cnt_l_max40 | mld_cnt_l_max25;

//  Count number of 64-bit block word transfers at XLGMII and control compensation flag.
//  -----------------------------------------------------
always @(posedge xlgmii_txclk or posedge reset_txclk)
   begin
   if (reset_txclk == 1'b 1)
      begin
      mld_norm40 <= 1'b 1;
      mld_norm25 <= 1'b 1;
      mld_cnt_l <= {16{1'b 0}};
      mld_cnt_tog <= 1'b 0;
      end
   else
      begin
      // ----------------------------
      if (TXIPG_MLDCOMP_DISABLE == 1)
         begin
         mld_norm40 <= 1'b 1;   // disabled compensation
         mld_norm25 <= 1'b 1;
         mld_cnt_l <= {16{1'b 0}};
         mld_cnt_tog <= 1'b 0;
         end
      // ----------------------------
      else if (ipg_comp_cnt == 16'h 0000)
         begin
         mld_norm40 <= 1'b 1;   // disabled compensation
         mld_norm25 <= 1'b 1;
         mld_cnt_l <= {16{1'b 0}};
         mld_cnt_tog <= 1'b 0;
         end
      else
         begin
         if (xlgmii_txclk_ena == 1'b 1)
            begin
            if (mld_cnt_l_max == 1'b 1)
               begin
               if (xgmii_mode == 1'b 1 & ipg_comp_cnt[0] == 1'b 1)   // 25G XGMII mode with odd compare value
                  begin
                  mld_cnt_l <= {{15{1'b 0}}, mld_cnt_tog};   // alternating start count from 0 or 1
                  end
               else   // 40G XLGMII mode or 25G XGMII mode with even compare value
                  begin
                  mld_cnt_l <= 16'd 1;   // count from 1 (to 16383) to be compatible with PCS settings
                  end
               end
            else
               begin
               mld_cnt_l <= mld_cnt_l + 16'h 0001;
               end

            // toggle counter init value for 25G XGMII mode with odd compare value
            if (mld_cnt_l_max25 == 1'b 1)
               begin
               mld_cnt_tog <= ~mld_cnt_tog;
               end

            // 40G XLGMII mode: set flag until idle has been removed, then clear it again.
            if (xgmii_mode == 1'b 1)
               begin
               mld_norm40 <= 1'b 1;   // disabled compensation
               end
            else if (mld_cnt_l_max40 == 1'b 1)
               begin
               mld_norm40 <= 1'b 0;   //  suppress DIC, remove IDLE when possible
               end
            else if (frame_eop == 1'b 1 & (mld_sub1[0] == 1'b 1 | txipg_sub1[0] == 1'b 1))
               begin
               //  idle was now removed, return to normal operation
               mld_norm40 <= 1'b 1;
               end

            // 25G XGMII mode: set flag until idle has been removed, then clear it again.
            if (xgmii_mode == 1'b 0)
               begin
               mld_norm25 <= 1'b 1;   // disabled compensation
               end
            else if (mld_cnt_l_max25 == 1'b 1)
               begin
               mld_norm25 <= 1'b 0;
               end
            else if (frame_eop == 1'b 1 & (dic_m4 == 1'b 1 | mld_sub1[1] == 1'b 1 | txipg_sub1[1] == 1'b 1))
               begin
               //  idle was now removed, return to normal operation
               mld_norm25 <= 1'b 1;
               end
            end
         end
      end
   end

// 25G marker compensation
// -----------------------
// Case 1: mod != 4:
// - wait until dic_cnt[2] is asserted or remains asserted at eop
// - subtract 4 from DIC count to remove 4-byte idle block in XGMII shifter (reset shifter)

// mod = 4 check (txipg_mod already considers CRC append)
assign dic_mod4 = (txipg_mod == 3'b 100) ? 1'b 1 : 1'b 0;

// IPG must not go below 8, hence must exclude certain DIC/mod combinations
assign do_shft_set = (dic_cnt[1:0] == 2'b 00 & (txipg_mod == 3'b 001 | txipg_mod == 3'b 010 | txipg_mod == 3'b 011)) |
                     (dic_cnt[1:0] == 2'b 01 & (txipg_mod == 3'b 001 | txipg_mod == 3'b 010)) |
                     (dic_cnt[1:0] == 2'b 10 & (txipg_mod == 3'b 001)) ? 1'b 1 : 1'b 0;

assign no_shft_clr = (dic_cnt[1:0] == 2'b 00 & (txipg_mod == 3'b 101 | txipg_mod == 3'b 110 | txipg_mod == 3'b 111)) |
                     (dic_cnt[1:0] == 2'b 01 & (txipg_mod == 3'b 101 | txipg_mod == 3'b 110)) |
                     (dic_cnt[1:0] == 2'b 10 & (txipg_mod == 3'b 101)) ? 1'b 1 : 1'b 0;

assign dic_m4 = (~mld_norm25 & ~dic_mod4 & ~dic_cnt[2] & dic_plus[2] & ~do_shft_set) |   // Case 1
                (~mld_norm25 & ~dic_mod4 &  dic_cnt[2] & dic_plus[2] & ~no_shft_clr) |
                (~mld_norm25 &  dic_mod4 &  dic_cnt[2]);   // Case 2.2

// Case 2: mod = 4:
// IPG = 12, DIC does not change.
// Case 2.1: dic_cnt[2] = 0 (currently no shift in XGMII shifter):
// - instruct 40G IPG logic to remove 8-byte idle block
// - add 4 to DIC count to add 4-byte idle block in XGMII shifter (activate shifter)

assign dic_a4 = ~mld_norm25 & dic_mod4 & ~dic_cnt[2];       // Case 2.1

// Case 2.2: dic_cnt[2] = 1 (currently shift in XGMII shifter):
// - subtract 4 from DIC count to remove 4-byte idle block in XGMII shifter (reset shifter)
// see dic_m4 above


endmodule // module mac40_tx_ipg
